import random
from typing import List

import torch

"""
设置随机数种子，确保实验结果的可重现性：
SEED = 42：定义随机种子值为42
random.seed(SEED)：设置Python标准库random模块的随机种子
torch.manual_seed(SEED)：设置PyTorch框架的随机种子
通过固定种子值，可以使随机数生成器产生相同的随机序列，保证每次运行代码得到一致的结果。
"""
SEED = 42
random.seed(SEED)
torch.manual_seed(SEED)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

"""
使用列表包含元组的数据结构有以下几个原因：
1.数据配对存储
每个元组包含两个相关的字符串：源语言句子和目标语言翻译
元组的不可变性保证了翻译对的完整性
2.便于数据处理
列表允许通过索引访问每个翻译对
可以轻松遍历所有翻译对进行处理
3.符合序列到序列任务需求
用于训练序列到序列(Seq2Seq)模型
每个元组代表一个输入-输出样本对
4.数据结构优势
pairs列表中的每个元素都是一个元组，包含源句子和目标句子
这种结构便于后续的批量处理和数据加载
这种数据组织方式在机器翻译等序列到序列任务中非常常见，便于模型训练时的输入输出配对处理。
"""
pairs = [
    ("i love you", "je t aime"),
    ("how are you", "comment ca va"),
    ("i am fine", "je vais bien"),
    ("thank you", "merci"),
    ("good morning", "bonjour"),
    ("see you later", "a plus tard"),
    ("i love nlp", "j aime nlp"),
    ("this is good", "c est bon"),
    ("where are you", "ou es tu"),
    ("i like apples", "j aime les pommes")
]

"""
定义了四个特殊的标记符常量：
PAD：填充标记，用于补齐序列长度
SOS：句子开始标记
EOS：句子结束标记
UNK：未知词标记，表示词汇表中不存在的词
这些标记在自然语言处理中常用于序列处理和神经网络训练。
"""
PAD, SOS, EOS, UNK = "<pad>", "<s>", "</s>", "<unk>"


class Vocab:
    def __init__(self):
        """
        词汇表的两种映射关系：
        self.stoi：String to Index（字符串到索引的映射）
        这是一个字典，将特殊标记符映射到对应的索引值
        PAD映射到0，SOS映射到1，EOS映射到2，UNK映射到3
        self.itos：Index to String（索引到字符串的映射）
        这是一个列表，通过索引位置访问对应的标记符
        索引0对应PAD，索引1对应SOS，索引2对应EOS，索引3对应UNK
        这种双向映射设计使得在自然语言处理中能够方便地在单词和索引之间进行转换，便于神经网络处理文本数据。
        """
        self.stoi = {PAD: 0, SOS: 1, EOS: 2, UNK: 3}  # 字典，用于存储单词和索引的映射关系
        self.itos = [PAD, SOS, EOS, UNK]  # 列表，用于存储索引到单词的映射关系

    def add_sentence(self, sentence: str):
        """
        将句子中的每个单词添加到词汇表中。
        遍历句子中每个单词，如果单词不在词汇表映射中，则将其添加到stoi字典（单词到索引的映射）和itos列表（索引到单词的映射）中，确保词汇表只包含唯一的单词。
        sentence.strip().split() 的作用是：
        sentence.strip()：移除句子开头和结尾的空白字符（如空格、制表符、换行符等）
        .split()：将去除首尾空白的句子按空格分割成单词列表
        """
        for tok in sentence.strip().split():
            if tok not in self.stoi:
                self.stoi[tok] = len(self.itos)
                self.itos.append(tok)

    def encode(self, sentence: str, add_sos_eos=True) -> List[int]:
        """
        将输入句子编码为整数序列：
        toks = sentence.strip().split() - 去除句子首尾空格并按空格分割成词列表
        ids = [self.stoi.get(tok, self.stoi[UNK]) for tok in toks] - 将每个词转换为对应的整数ID，未登录词用UNK标记
        根据add_sos_eos参数决定是否在序列前后添加开始(SOS)和结束(EOS)标记
        """
        toks = sentence.strip().split()
        ids = [self.stoi.get(tok, self.stoi[UNK]) for tok in toks]
        if add_sos_eos:
            return [self.stoi[SOS]] + ids + [self.stoi[EOS]]
        else:
            return ids

    def decode(self, ids: List[int]) -> str:
        """
        将整数ID序列解码为字符串：
        遍历输入的ID列表
        遇到结束符(EOS)时停止解码
        跳过开始符(SOS)和填充符(PAD)
        将有效的ID转换为对应的词汇
        用空格连接所有词汇返回字符串结果
        """
        toks = []
        for i in ids:
            if i == self.stoi[EOS]:
                break
            if i in (self.stoi[SOS], self.stoi[PAD]):
                continue
            toks.append(self.itos[i])
        return " ".join(toks)

    @property
    def size(self):
        """
        定义了一个名为size的属性方法，用于返回词汇表的大小。
        它通过计算self.itos列表的长度来确定词汇表中词元的数量。
        @property装饰器使得这个方法可以像访问属性一样被调用，而不需要使用括号。
        """
        return len(self.itos)


"""
创建源语言和目标语言两个词汇表对象
遍历所有的句子对(pairs)
将每个句子对中的源语言句子和目标语言句子分别添加到对应的词汇表中
用于构建神经网络翻译模型的词汇表。
"""
src_vocab = Vocab()
tgt_vocab = Vocab()
for src, tgt in pairs:
    src_vocab.add_sentence(src)
    tgt_vocab.add_sentence(tgt)

if __name__ == '__main__':
    print(src_vocab.stoi)
    print(src_vocab.itos)
